/*
JETware Hands-free Extension provides additional features for Windows Mobile HFP support.
Copyright (C) 2004-2009 Jim Thatcher

This program is free software; you can redistribute it and/or modify it under the terms of the 
GNU General Public License as published by the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; 
if not, write to the Free Software Foundation, Inc., 
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

Source Control header

$Workfile: StatusMonitor.cpp $
$Revision: 29 $
$Date: 6/05/07 1:49p $
*/
// Include the proper version of TAPI
#include "stdafx.h"
#include <windows.h>
#include "BTHFExt.h"
#include <tapi.h>
#include <extapi.h>
#include <pm.h>
#include "msgqueue.h"
#ifdef USE_NOTIFY
#include "snapi.h"
#include "regext.h"
#include "NotifyMgr.h"
CNotifyMgr * g_pNotify = NULL;
#endif

TCHAR g_szAppName[] = TEXT("JETBTHFx");
// Main window class name

HLINEAPP g_hLineApp = NULL;     // Application's use handle for TAPI
DWORD g_dwVoiceLineID = -1;   // Current line device identifier
HLINE g_hVoiceLine = NULL;
// information

BOOL g_bReportCID = FALSE;
BOOL g_bReportECID = FALSE;
DWORD g_dwBatteryLevel = 1;
DWORD g_dwSignalLevel = 1;
BOOL g_bRoaming = FALSE;
BOOL g_bInCall = FALSE;
BOOL g_bService = FALSE;
DWORD g_dwCallSetup = 0;
#ifdef DEBUG
DWORD g_dwNumCalls = 0;
#endif
DWORD g_dwNumVoiceCalls = 0;
HANDLE hBAG = INVALID_HANDLE_VALUE;
HANDLE hMonitorThread = NULL;
HANDLE hKillMonitor = NULL;
HANDLE hCallState = NULL;
HANDLE hPowerDummyEvent = NULL;
HANDLE hNotifyDummyEvent = NULL;
BOOL bInSession = FALSE;
DWORD g_dwBagPowerReq = -1;

#define CALL_OUTGOING	0x80000000

typedef struct tagCALLLIST
{
	HCALL			Call;
	BOOL			IsVoice;
	DWORD			CallState;
	DWORD			ID;
	tagCALLLIST *	Next;
} CALLLIST, FAR * LPCALLLIST;

LPCALLLIST g_CallList = NULL;

BOOL DeleteCall(HCALL hCall)
{
	LPCALLLIST pCurr = g_CallList;
	LPCALLLIST pPrev = NULL;
	BOOL bRet = FALSE;
	while (pCurr != NULL)
	{
		if (pCurr->Call == hCall)
			break;
		pPrev = pCurr;
		pCurr = pCurr->Next;
	}
	if (pCurr->Call == hCall) // was the call in our list?
	{
		if (pPrev != NULL)
			pPrev->Next = pCurr->Next;
		else // This is first call on list, set global list to next
			g_CallList = pCurr->Next;
		if (pCurr->IsVoice)
		{
			EnterCriticalSection(&csStatus);
			if (g_dwNumVoiceCalls > 1)
				g_dwNumVoiceCalls--;
			else
			{
				g_dwNumVoiceCalls = 0;
				g_bInCall = FALSE;
				if (g_bReportStatus && g_bNeedExtraStatus)
				{
					PostResponse("+CIEV: 2,0");
				}
			}
			LeaveCriticalSection(&csStatus);
			if (hCallState != NULL)
				SetEvent(hCallState); // let the monitor thread know we have disconnected call
		}
		LocalFree(pCurr);
#ifdef DEBUG
		g_dwNumCalls--;
#endif
		bRet = TRUE;
	}
#ifdef DEBUG
	else
		LogString("Error deleting call not in list", DEBUG_TAPI);
#endif
	if (hCall != NULL)
		lineDeallocateCall(hCall);
	return bRet;
}

void DeleteAllCalls()
{
	while (g_CallList != NULL)
	{
		DeleteCall(g_CallList->Call);
	}
}

UINT NumHeldCalls()
{
	int nCount = 0;
	LPCALLLIST pCurr = g_CallList;
	while (pCurr != NULL)
	{
		if (pCurr->CallState & LINECALLSTATE_ONHOLD)
			nCount++;
		pCurr = pCurr->Next;
	}
	return nCount;
}

void CallIsVoice(HCALL hCall)
{
	LPCALLLIST pCurr = g_CallList;
	while(pCurr != NULL)
	{
		if (pCurr->Call == hCall)
		{
			if (!pCurr->IsVoice)
			{
				pCurr->IsVoice = TRUE;
				g_dwNumVoiceCalls++;
			}
			break;
		}
		pCurr = pCurr->Next;
	}
	if (hCallState != NULL)
		SetEvent(hCallState); // let the monitor thread know we are in a call
}

void SetCallState(HCALL hCall, DWORD dwState)
{
	LPCALLLIST pCurr = g_CallList;
	while(pCurr != NULL)
	{
		if (pCurr->Call == hCall)
		{
			if (pCurr->CallState & CALL_OUTGOING)
				pCurr->CallState = dwState | CALL_OUTGOING;
			else
				pCurr->CallState = dwState;
			break;
		}
		pCurr = pCurr->Next;
	}
}

BOOL AddCall(HCALL hCall)
{
	LPCALLLIST pCurr = g_CallList;
	if (pCurr == NULL) // handle case where list is empty
	{
		g_CallList = (LPCALLLIST) LocalAlloc(LPTR, sizeof(CALLLIST));
		pCurr = g_CallList;
	}
	else
	{
		while (pCurr->Next != NULL)
			pCurr = pCurr->Next;
		pCurr->Next = (LPCALLLIST) LocalAlloc(LPTR, sizeof(CALLLIST));
		pCurr = pCurr->Next;
	}
	if (pCurr != NULL)
	{
		DWORD dwSize = sizeof(LINECALLINFO);
		LPLINECALLINFO pInfo = NULL;
		pCurr->Call = hCall;
		pCurr->IsVoice = FALSE;
		pCurr->Next = NULL;
		pCurr->ID = g_dwNumVoiceCalls;
#ifdef DEBUG
		CHAR szReport[64];
		g_dwNumCalls++;
#endif

		while (pInfo == NULL)
		{
			pInfo = (LPLINECALLINFO) LocalAlloc(LPTR, dwSize);
			if (pInfo == NULL)
			{
#ifdef DEBUG
				strcpy(szReport, "Memory error in lineGetCallInfo");
				LogString(szReport, DEBUG_TAPI);
#endif
				break;
			}
			pInfo->dwTotalSize = dwSize;
			DWORD dwRet = lineGetCallInfo(hCall, pInfo);
			if (dwRet == LINEERR_STRUCTURETOOSMALL)
			{
				dwSize = pInfo->dwNeededSize;
				LocalFree(pInfo);
				pInfo = NULL;
			}
			else if (dwRet != 0) // any other failure
			{
#ifdef DEBUG
				sprintf(szReport, "Error %8.8X in lineGetCallInfo", dwRet);
				LogString(szReport, DEBUG_TAPI);
#endif
				LocalFree(pInfo);
				pInfo = NULL;
				break;
			}
		}
		if (pInfo != NULL)
		{
			pCurr->IsVoice = (pInfo->dwMediaMode & LINEMEDIAMODE_INTERACTIVEVOICE) ? TRUE : FALSE;
			if (pCurr->IsVoice)
				g_dwNumVoiceCalls++;
			pCurr->ID = pInfo->dwCallID;
#ifdef DEBUG
			sprintf(szReport, "AddCall-ID=%i, Mode=%8.8X", pInfo->dwCallID, pInfo->dwMediaMode);
			LogString(szReport, DEBUG_TAPI);
#endif
			LocalFree(pInfo);
		}
		return TRUE;
	}
	else
	{
#ifdef DEBUG
		LogString("Memory allocation failed in AddCall", DEBUG_TAPI);
#endif
		return FALSE;
	}
}

void CleanupTAPI()
{
	DeleteAllCalls();
	if (g_hLineApp != NULL)
	{
		if (g_hVoiceLine != NULL)
		{
			lineClose(g_hVoiceLine);
			g_hVoiceLine = NULL;
			g_dwVoiceLineID = -1;
		}
		lineShutdown(g_hLineApp);
		g_hLineApp = NULL;
	}
}

BOOL GetOperator(TCHAR * szOperator)
{
	BOOL bRet = FALSE;
#ifdef USE_NOTIFY
	if (g_pNotify == NULL)
	{
#endif
	LINEOPERATOR lo;
	if (g_hVoiceLine != NULL)
	{
		DWORD dwRet = lineGetCurrentOperator(g_hVoiceLine, &lo);
		if (!dwRet)
		{
			bRet = TRUE;
			if (lo.dwValidFields & LINEOPFORMAT_ALPHALONG)
				lstrcpy(szOperator, lo.lpszLongName);
			else if (lo.dwValidFields & LINEOPFORMAT_ALPHASHORT)
				lstrcpy(szOperator, lo.lpszShortName);
			else if (lo.dwValidFields & LINEOPFORMAT_NUMERIC)
				lstrcpy(szOperator, lo.lpszNumName);
			else
				lstrcpy(szOperator, _T("Unknown"));
		}
	}
#ifdef USE_NOTIFY
	}
	else
	{
		g_pNotify->StartIndicators();
		g_pNotify->GetOperator(szOperator, 48);
		bRet = TRUE;
	}
#endif
	return bRet;
}

void UpdateIndicators()
{
#ifdef USE_NOTIFY
	if (g_pNotify == NULL)
	{
#endif
	DWORD dwSize = sizeof(LINEDEVSTATUS) + 32;
	DWORD dwReturn = 0;
#ifdef DEBUG
	CHAR szReport[64];
#endif
	// get current registration state
	DWORD dwReg = 0;
	dwReturn = lineGetRegisterStatus(g_hVoiceLine, &dwReg);
	if (!dwReturn)
	{
		g_bRoaming = (dwReg == LINEREGSTATUS_ROAM) ? TRUE : FALSE;
		g_bRoaming = (g_dwInvertRoam) ? !g_bRoaming : g_bRoaming;
		g_bService = (dwReg > LINEREGSTATUS_ATTEMPTING) ? TRUE : FALSE;
	}
	LPLINEDEVSTATUS lineDevStatus = (LPLINEDEVSTATUS) LocalAlloc(LPTR, dwSize);
	// read current state of device, send unsolicited responses for any changes
	if (lineDevStatus == NULL)
		return;

	lineDevStatus->dwTotalSize = dwSize;
	dwReturn = lineGetLineDevStatus(g_hVoiceLine, lineDevStatus);
	if (dwReturn == LINEERR_STRUCTURETOOSMALL)
	{
		dwSize = (lineDevStatus->dwNeededSize > dwSize) ? 
			lineDevStatus->dwNeededSize : dwSize + 64;
		LocalFree(lineDevStatus);
		lineDevStatus = (LPLINEDEVSTATUS) LocalAlloc(LPTR, dwSize);
		dwReturn = lineGetLineDevStatus(g_hVoiceLine, lineDevStatus);
	}
	if (!dwReturn)
	{
		CHAR szReply[20];
		DWORD dwCurrSignal = (DWORD)(((lineDevStatus->dwSignalLevel + 0x1999) * 5) / 0xFFFF);
		DWORD dwLastReportedSignal = 0;
		EnterCriticalSection(&csStatus);
		dwLastReportedSignal = ((g_dwSignalLevel + 0x1999) * 5) / 0xFFFF;
		g_dwSignalLevel = lineDevStatus->dwSignalLevel;
		// don't report change in incall status since internal system is doing that
		// just track the state in order to respond to AT+CIND? requests
#ifdef DEBUG
		sprintf(szReport, "Signal raw = %8.8X, level = %i, service = %i", lineDevStatus->dwSignalLevel, dwCurrSignal, g_bService);
		LogString(szReport, DEBUG_TAPI);
#endif
		// If we have no service, don't trust signal level
		if ((lineDevStatus->dwSignalLevel > 0) && !g_bService)
		{
			g_dwSignalLevel = 0;
			dwCurrSignal = 0;
		}
		if (dwCurrSignal != dwLastReportedSignal)
		{
			// g_dwSignalLevel = (DWORD)((lineDevStatus->dwSignalLevel + 0x1999) / 0x3333);
			if (g_bReportStatus)
			{
				sprintf(szReply, "+CIEV: 5,%i", dwCurrSignal);
				PostResponse(szReply);
			}
		}
		LeaveCriticalSection(&csStatus);
	}
#ifdef DEBUG
	else
	{
		sprintf(szReport, "Error %8.8X in lineGetDevStatus", dwReturn);
		LogString(szReport, DEBUG_TAPI);
	}
#endif

	LocalFree(lineDevStatus);
#ifdef USE_NOTIFY
	} // if (g_pNotify == NULL)
#endif
}

void GetBatteryLevel()
{
	SYSTEM_POWER_STATUS_EX2 ps;
	DWORD dwRet = 0;
	dwRet = GetSystemPowerStatusEx2(&ps, sizeof(ps), FALSE);
	if (dwRet != 0)
	{
		DWORD dwBattLevel = (ps.BatteryLifePercent < 90) ? ((ps.BatteryLifePercent + 10) / 20) : 5;
		DWORD dwLastReportBattLevel = (g_dwBatteryLevel < 90) ? ((g_dwBatteryLevel + 10) / 20) : 5;
		EnterCriticalSection(&csStatus);
		g_dwBatteryLevel = ps.BatteryLifePercent;
		LeaveCriticalSection(&csStatus);
		/*
		if (dwBattLevel != dwLastReportBattLevel)
		{
			// g_dwBatteryLevel = dwBattLevel;
			if (g_bReportStatus)
			{
				CHAR szReply[20];
				sprintf(szReply, "+CIEV: 4,%i", dwBattLevel);
				PostResponse(szReply);
			}
		}
		*/
	}
}

BOOL IsVoiceCall(HCALL hCall)
{
	BOOL bReturn = FALSE;
#ifdef DEBUG
	CHAR szReport[64];
#endif
	DWORD dwSize = sizeof(LINECALLINFO);
	LPLINECALLINFO pInfo = NULL;
	while (pInfo == NULL)
	{
		pInfo = (LPLINECALLINFO) LocalAlloc(LPTR, dwSize);
		if (pInfo == NULL)
		{
#ifdef DEBUG
			strcpy(szReport, "Memory error in lineGetCallInfo");
			LogString(szReport, DEBUG_TAPI);
#endif
			break;
		}
		pInfo->dwTotalSize = dwSize;
		DWORD dwRet = lineGetCallInfo(hCall, pInfo);
		if (dwRet == LINEERR_STRUCTURETOOSMALL)
		{
			dwSize = pInfo->dwNeededSize;
			LocalFree(pInfo);
			pInfo = NULL;
		}
		else if (dwRet != 0) // any other failure
		{
#ifdef DEBUG
			sprintf(szReport, "Error %8.8X in lineGetCallInfo", dwRet);
			LogString(szReport, DEBUG_TAPI);
#endif
			LocalFree(pInfo);
			pInfo = NULL;
			break;
		}
	}
	if (pInfo != NULL)
	{
		bReturn = (pInfo->dwMediaMode & LINEMEDIAMODE_INTERACTIVEVOICE) ? TRUE : FALSE;
#ifdef DEBUG
		sprintf(szReport, "IsVoiceCall-CallID=%i, Mode=%8.8X", pInfo->dwCallID, pInfo->dwMediaMode);
		LogString(szReport, DEBUG_TAPI);
#endif
		LocalFree(pInfo);
	}
	return bReturn;
}

DWORD GetIMSI(CHAR * szIMSI, DWORD dwSizeIMSI)
{
	memset(szIMSI, 0, dwSizeIMSI);
#ifdef DEBUG
	CHAR szReport[64];
#endif
	DWORD dwSize = sizeof(LINEGENERALINFO);
	DWORD dwRet = LINEERR_OPERATIONFAILED;
	LPLINEGENERALINFO pInfo = NULL;
	if (g_dwReportIMI)
	{
		while (pInfo == NULL)
		{
			pInfo = (LPLINEGENERALINFO) LocalAlloc(LPTR, dwSize);
			if (pInfo == NULL)
			{
#ifdef DEBUG
				strcpy(szReport, "Memory error in lineGetGeneralInfo");
				LogString(szReport, DEBUG_TAPI);
#endif
				break;
			}
			pInfo->dwTotalSize = dwSize;
			dwRet = lineGetGeneralInfo(g_hVoiceLine, pInfo);
			if ((dwRet == LINEERR_STRUCTURETOOSMALL) || (dwSize < pInfo->dwNeededSize))
			{
				dwSize = pInfo->dwNeededSize;
				LocalFree(pInfo);
				pInfo = NULL;
			}
			else if (dwRet != 0) // any other failure
			{
#ifdef DEBUG
				sprintf(szReport, "Error %8.8X in lineGetGeneralInfo", dwRet);
				LogString(szReport, DEBUG_TAPI);
#endif
				LocalFree(pInfo);
				pInfo = NULL;
				break;
			}
		}
	}
	if (pInfo != NULL)
	{
		TCHAR * pUniImsi = (TCHAR *) ((BYTE *) pInfo + pInfo->dwSubscriberNumberOffset);
		int nRet = 0;
#ifdef DEBUG
		sprintf(szReport, "Subscriber = %ls, size=%i", pUniImsi, pInfo->dwSubscriberNumberSize);
		LogString(szReport, DEBUG_INFO);
#endif
		if (pInfo->dwSubscriberNumberSize >= 15*sizeof(TCHAR))
		{
			nRet = WideCharToMultiByte(CP_UTF8, 0, pUniImsi, (pInfo->dwSubscriberNumberSize/sizeof(TCHAR))-1, szIMSI, dwSizeIMSI, NULL, NULL);
#ifdef DEBUG
			sprintf(szReport, "UTF8 Subscriber = %s", szIMSI);
			LogString(szReport, DEBUG_INFO);
#endif
		}
		else
		{
			// Handle non-GSM phones with short numbers reported as subscriber number (Treo 700w gives 8-digit hex number)
			TCHAR szCountry[16];
			// CHAR szSN[16];
			// nRet = WideCharToMultiByte(CP_UTF8, 0, pUniImsi, pInfo->dwSubscriberNumberSize, szSN, sizeof(szSN), NULL, NULL);
			int nSN = 0;
			int nCountry = 310; // default to US
			int nCarrier = 0; // Don't think anything will look at this part
			int nCount = swscanf(pUniImsi, _T("%i"), &nSN);
#ifdef DEBUG
			sprintf(szReport, "s/n=%i, nCount=%i", nSN, nCount);
			LogString(szReport, DEBUG_INFO);
#endif
			if (GetLocaleInfo(LOCALE_SYSTEM_DEFAULT, LOCALE_SABBREVCTRYNAME, (LPTSTR) &szCountry, (sizeof(szCountry)/sizeof(TCHAR))))
			{
				if (wcsncmp(szCountry, _T("US"), 2) == 0)
					nCountry = 310;
				else if (wcsncmp(szCountry, _T("JP"), 2) == 0)
					nCountry = 440;
				else if (wcsncmp(szCountry, _T("KR"), 2) == 0)
					nCountry = 450;
			}
			memset(szIMSI, 0, dwSizeIMSI);
			sprintf(szIMSI, "%3.3i%3.3i%9.9i", nCountry, nCarrier, nSN);
#ifdef DEBUG
			sprintf(szReport, "Generated Subscriber = %s", szIMSI);
			LogString(szReport, DEBUG_INFO);
#endif
		}
		if (g_dwReportIMI == 2) // mask subscriber specific portion of IMI
		{
			memset(&(szIMSI[6]), '0', sizeof(CHAR)*9);
		}
		LocalFree(pInfo);
	}
	return dwRet;
}

DWORD GetIMEI(CHAR * szIMEI, DWORD dwSizeIMEI)
{
	szIMEI[0] = 0;
#ifdef DEBUG
	CHAR szReport[64];
#endif
	DWORD dwRet = 0;
	DWORD dwSize = sizeof(LINEGENERALINFO);
	LPLINEGENERALINFO pInfo = NULL;
	while (pInfo == NULL)
	{
		pInfo = (LPLINEGENERALINFO) LocalAlloc(LPTR, dwSize);
		if (pInfo == NULL)
		{
#ifdef DEBUG
			strcpy(szReport, "Memory error in lineGetGeneralInfo");
			LogString(szReport, DEBUG_INFO);
#endif
			break;
		}
		pInfo->dwTotalSize = dwSize;
		dwRet = lineGetGeneralInfo(g_hVoiceLine, pInfo);
		if ((dwRet == LINEERR_STRUCTURETOOSMALL) || (dwSize < pInfo->dwNeededSize))
		{
			dwSize = pInfo->dwNeededSize;
			LocalFree(pInfo);
			pInfo = NULL;
		}
		else if (dwRet != 0) // any other failure
		{
#ifdef DEBUG
			sprintf(szReport, "Error %8.8X in lineGetGeneralInfo", dwRet);
			LogString(szReport, DEBUG_INFO);
#endif
			LocalFree(pInfo);
			pInfo = NULL;
			break;
		}
	}
	if (pInfo != NULL)
	{
		TCHAR * pUniImei = (TCHAR *) ((BYTE *) pInfo + pInfo->dwSerialNumberOffset);
#ifdef DEBUG
		TCHAR * pUniString = (TCHAR *) ((BYTE *) pInfo + pInfo->dwManufacturerOffset);
		sprintf(szReport, "Mfr = %ls, size=%i", pUniString, pInfo->dwManufacturerSize);
		LogString(szReport, DEBUG_INFO);
		pUniString = (TCHAR *) ((BYTE *) pInfo + pInfo->dwModelOffset);
		sprintf(szReport, "Model = %ls, size=%i", pUniString, pInfo->dwModelSize);
		LogString(szReport, DEBUG_INFO);
		pUniString = (TCHAR *) ((BYTE *) pInfo + pInfo->dwRevisionOffset);
		sprintf(szReport, "Rev = %ls, size=%i", pUniString, pInfo->dwRevisionSize);
		LogString(szReport, DEBUG_INFO);
		pUniString = (TCHAR *) ((BYTE *) pInfo + pInfo->dwSubscriberNumberOffset);
		sprintf(szReport, "Subscriber = %ls, size=%i", pUniString, pInfo->dwSubscriberNumberSize);
		LogString(szReport, DEBUG_INFO);
		sprintf(szReport, "Unicode IMEI=%ls, size=%i", pUniImei, pInfo->dwSerialNumberSize);
		LogString(szReport, DEBUG_INFO);
#endif
		int nRet = WideCharToMultiByte(CP_UTF8, 0, pUniImei, (pInfo->dwSerialNumberSize/sizeof(TCHAR))-1, szIMEI, dwSizeIMEI, NULL, NULL);
		LocalFree(pInfo);
	}
	return dwRet;
}

void ReadHardwareDeviceString(TCHAR * szDeviceString, WORD wFlags)
{
#ifdef DEBUG
	CHAR szReport[64];
#endif

	if ((g_hVoiceLine != NULL) && (wFlags & (HWINFO_MFR | HWINFO_MODEL | HWINFO_VER)))
	{
		DWORD dwRet = 0;
		DWORD dwSize = sizeof(LINEGENERALINFO);
		LPLINEGENERALINFO pInfo = NULL;
		
		while (pInfo == NULL)
		{
			pInfo = (LPLINEGENERALINFO) LocalAlloc(LPTR, dwSize);
			if (pInfo == NULL)
			{
	#ifdef DEBUG
				strcpy(szReport, "Memory error in lineGetGeneralInfo");
				LogString(szReport, DEBUG_INFO);
	#endif
				break;
			}
			pInfo->dwTotalSize = dwSize;
			dwRet = lineGetGeneralInfo(g_hVoiceLine, pInfo);
			if ((dwRet == LINEERR_STRUCTURETOOSMALL) || (dwSize < pInfo->dwNeededSize))
			{
				dwSize = pInfo->dwNeededSize;
				LocalFree(pInfo);
				pInfo = NULL;
			}
			else if (dwRet != 0) // any other failure
			{
	#ifdef DEBUG
				sprintf(szReport, "Error %8.8X in lineGetGeneralInfo", dwRet);
				LogString(szReport, DEBUG_INFO);
	#endif
				LocalFree(pInfo);
				pInfo = NULL;
				break;
			}
		}
		if (pInfo != NULL)
		{
			TCHAR * pUniString = (TCHAR *) ((BYTE *) pInfo + pInfo->dwManufacturerOffset);
			if ((wFlags & HWINFO_MFR) && (pInfo->dwManufacturerSize > 0))
			{
				if (*szDeviceString != 0)
					lstrcat(szDeviceString, _T(" "));
				lstrcat(szDeviceString, pUniString);
			}
			pUniString = (TCHAR *) ((BYTE *) pInfo + pInfo->dwModelOffset);
			if ((wFlags & HWINFO_MODEL) && (pInfo->dwModelSize > 0))
			{
				if (*szDeviceString != 0)
					lstrcat(szDeviceString, _T(" "));
				lstrcat(szDeviceString, pUniString);
			}
			pUniString = (TCHAR *) ((BYTE *) pInfo + pInfo->dwRevisionOffset);
			if ((wFlags & HWINFO_VER) && (pInfo->dwRevisionSize > 0))
			{
				if (*szDeviceString != 0)
					lstrcat(szDeviceString, _T(" "));
				lstrcat(szDeviceString, pUniString);
			}
			LocalFree(pInfo);
		}
	}
	else if (wFlags & HWINFO_OEM)
	{
		TCHAR szOemString[MAX_PATH];
		BOOL bRet = SystemParametersInfo(SPI_GETOEMINFO, MAX_PATH, szOemString, 0);
		if (bRet)
		{
			wcsncpy(szDeviceString, szOemString, min(MAX_PATH - 4, wcslen(szOemString)+1));
		}
	}
	return;
}

DWORD GetCallStatus(HCALL hCall)
{
	DWORD dwReturn = 0;
	LPLINECALLSTATUS pStat = NULL;
	DWORD dwSize = sizeof(LINECALLSTATUS) + 32;
#ifdef DEBUG
	CHAR szReport[64];
#endif
	while (pStat == NULL)
	{
		pStat = (LPLINECALLSTATUS) LocalAlloc(LPTR, dwSize);
		if (pStat == NULL)
		{
#ifdef DEBUG
			strcpy(szReport, "Memory error in lineGetCallStatus");
			LogString(szReport, DEBUG_TAPI);
#endif
			break;
		}
		pStat->dwTotalSize = dwSize;
		DWORD dwRet = lineGetCallStatus(hCall, pStat);
		if (dwRet == LINEERR_STRUCTURETOOSMALL)
		{
			dwSize = pStat->dwNeededSize;
			LocalFree(pStat);
			pStat = NULL;
		}
		else if (dwRet != 0) // any other failure
		{
#ifdef DEBUG
			sprintf(szReport, "Error %8.8X in lineGetCallStatus", dwRet);
			LogString(szReport, DEBUG_TAPI);
#endif
			LocalFree(pStat);
			pStat = NULL;
			break;
		}
	}
	if (pStat != NULL)
	{
#ifdef DEBUG
		sprintf(szReport, "Call status is %8.8X in lineGetCallStatus", pStat->dwCallState);
		LogString(szReport, DEBUG_TAPI);
#endif
		dwReturn = pStat->dwCallState;
		LocalFree(pStat);
	}
	return dwReturn;
}

typedef struct tagRESPONSE_ITEM
{
	CHAR *				Response;
	tagRESPONSE_ITEM *	Next;
} RESPONSE_ITEM, * LPRESPONSE_ITEM;

LPRESPONSE_ITEM ResponseQueue = NULL;
LPRESPONSE_ITEM NewMailQueue = NULL;

// Queue up unsolicited responses
void PostResponse(LPSTR szResponse)
{
	WORD wLen = strlen(szResponse)+2;
	EnterCriticalSection(&csURQ);
	LPRESPONSE_ITEM pCurr = ResponseQueue;
	LPRESPONSE_ITEM pPrev = NULL;
	if (pCurr == NULL) // handle case where list is empty
	{
		ResponseQueue = (LPRESPONSE_ITEM) LocalAlloc(LPTR, sizeof(RESPONSE_ITEM));
		pCurr = ResponseQueue;
	}
	else
	{
		while (pCurr->Next != NULL)
		{
			pCurr = pCurr->Next;
		}
		pCurr->Next = (LPRESPONSE_ITEM) LocalAlloc(LPTR, sizeof(RESPONSE_ITEM));
		pPrev = pCurr;
		pCurr = pCurr->Next;
	}
	if (pCurr != NULL)
	{
		pCurr->Response = (CHAR *) LocalAlloc(LPTR, wLen);
		if (pCurr->Response == NULL)
		{
			pPrev->Next = NULL;
			LocalFree(pCurr);
#ifdef DEBUG
			LogString("Memory error in PostResponse", DEBUG_INFO);
#endif
		}
		strcpy(pCurr->Response, szResponse);
		pCurr->Next = NULL;
	}
	LeaveCriticalSection(&csURQ);
	// For 1.22 simplest change to eliminate delay after posting is to immediately signal a call state change
	SetEvent(hCallState);
}

// Queue up unsolicited responses
void PostNewMail(LPSTR szResponse)
{
	WORD wLen = strlen(szResponse)+2;
	// Reuse CS protecting Unsolicited Responses Queue
	EnterCriticalSection(&csURQ);
	LPRESPONSE_ITEM pCurr = NewMailQueue;
	LPRESPONSE_ITEM pPrev = NULL;
	if (pCurr == NULL) // handle case where list is empty
	{
		NewMailQueue = (LPRESPONSE_ITEM) LocalAlloc(LPTR, sizeof(RESPONSE_ITEM));
		pCurr = NewMailQueue;
	}
	else
	{
		while (pCurr->Next != NULL)
		{
			pCurr = pCurr->Next;
		}
		pCurr->Next = (LPRESPONSE_ITEM) LocalAlloc(LPTR, sizeof(RESPONSE_ITEM));
		pPrev = pCurr;
		pCurr = pCurr->Next;
	}
	if (pCurr != NULL)
	{
		pCurr->Response = (CHAR *) LocalAlloc(LPTR, wLen);
		if (pCurr->Response == NULL)
		{
			pPrev->Next = NULL;
			LocalFree(pCurr);
#ifdef DEBUG
			LogString("Memory error in PostResponse", DEBUG_INFO);
#endif
		}
		strcpy(pCurr->Response, szResponse);
		pCurr->Next = NULL;
	}
	LeaveCriticalSection(&csURQ);
	// For 1.22 simplest change to eliminate delay after posting is to immediately signal a call state change
	SetEvent(hCallState);
}

void ProcessNewMail()
{
	EnterCriticalSection(&csURQ);
	LPRESPONSE_ITEM pCurr = NewMailQueue;
	LeaveCriticalSection(&csURQ);
	while (pCurr != NULL && !bProcessingNewMail)
	{
		//Use event instead of bProcessingNewMail because bProcessingNewMail kicks us out of loop
		LogString(pCurr->Response, LOG_SMS_NOTIFY);
		EnterCriticalSection(&csURQ);
		bProcessingNewMail = TRUE;
		LeaveCriticalSection(&csURQ);
		SendATCommand(pCurr->Response);
		EnterCriticalSection(&csURQ);
		NewMailQueue = pCurr->Next;
		LocalFree(pCurr->Response);
		LocalFree(pCurr);
		pCurr = NewMailQueue;
		LeaveCriticalSection(&csURQ);
	}
}

void ProcessResponses()
{
	EnterCriticalSection(&csURQ);
	LPRESPONSE_ITEM pCurr = ResponseQueue;
	LeaveCriticalSection(&csURQ);
	while (pCurr != NULL)
	{
		LogString(pCurr->Response, LOG_NOTIFICATIONS);
		SendATCommand(pCurr->Response);
		EnterCriticalSection(&csURQ);
		ResponseQueue = pCurr->Next;
		LocalFree(pCurr->Response);
		LocalFree(pCurr);
		pCurr = ResponseQueue;
		LeaveCriticalSection(&csURQ);
		// WaitForSingleObject(hKillMonitor, 500); // delay 1/2 second
	}
	ProcessNewMail();
}

void EmptyResponseQueues()
{
	EnterCriticalSection(&csURQ);
	LPRESPONSE_ITEM pCurr = ResponseQueue;
	while (pCurr != NULL)
	{
		ResponseQueue = pCurr->Next;
		LocalFree(pCurr->Response);
		LocalFree(pCurr);
		pCurr = ResponseQueue;
	}
	pCurr = NewMailQueue;
	while (pCurr != NULL)
	{
		ResponseQueue = pCurr->Next;
		LocalFree(pCurr->Response);
		LocalFree(pCurr);
		pCurr = ResponseQueue;
	}
	LeaveCriticalSection(&csURQ);
}

/* FIX: 1.22 - Use thread ID to track start/stop of HFP session
BOOL IsHFConnected()
{
	// TODO: On WM5 and later look at StatStore first
	BOOL bRet = FALSE;
	if (hBAG != INVALID_HANDLE_VALUE && bInSession)
	{
		bRet = DeviceIoControl(hBAG,IOCTL_AG_OPEN_CONTROL,NULL,0,NULL,0,NULL,NULL);
	}
	// Options to keep PocketPC-based phones from disconnecting from BT
	if (bRet && (g_dwFeatureMask & FM_FORCE_POWER_ON))
		SystemIdleTimerReset();
	if (bRet && (g_dwFeatureMask & FM_PING_HF_AUDIO))
	{
		ConnectAudio();
		DisconnectAudio();
	}
	return bRet;
}
*/

#define WAIT_POWER_INDEX 2
#define WAIT_NOTIFY_INDEX 3

DWORD WINAPI MonitorThreadProc(LPVOID lpParam)
{
	DWORD dwWaitTime = 1000;
	DWORD dwTimeSincePingedHF = 60000; // since we are initializing session thread at startup, set for early reset
	HANDLE waitFor[4] = {hKillMonitor, hCallState, hPowerDummyEvent, hNotifyDummyEvent}; // hPowerQ, hNotifyQ };
	HANDLE hPowerNotifications = NULL;
	CEDEVICE_POWER_STATE ps = (CEDEVICE_POWER_STATE) g_dwBagPowerReq;
	HANDLE hPowerReq = NULL;
	TCHAR szBtDevName[8];
#ifdef DEBUG
	CHAR szLog[128];
#endif
#ifdef USE_NOTIFY
	if (IsNotifyAvailable())
	{
		waitFor[WAIT_NOTIFY_INDEX] = NULL;
		g_pNotify = new CNotifyMgr(&waitFor[WAIT_NOTIFY_INDEX]);
		if (waitFor[WAIT_NOTIFY_INDEX] == NULL)
		{
			delete g_pNotify;
			g_pNotify = NULL;
			waitFor[WAIT_NOTIFY_INDEX] = hNotifyDummyEvent;
		}
	}
#ifdef DEBUG
	sprintf(szLog, "CreateMsgQueue for notifications returned %8.8X, g_pNotify=%8.8X", waitFor[WAIT_NOTIFY_INDEX], g_pNotify);
	LogString(szLog, DEBUG_INDICATORS);
#endif
#endif

    DWORD dwPowerMsgSize = sizeof(POWER_BROADCAST) + (MAX_PATH * sizeof(TCHAR));

	// Initialize Power notification MSGQUEUEOPTIONS structure
	MSGQUEUEOPTIONS qOptions;
	qOptions.dwSize = sizeof(MSGQUEUEOPTIONS); 
	qOptions.dwFlags = MSGQUEUE_NOPRECOMMIT;
	qOptions.dwMaxMessages = 4; // This may be limiting number of occurances of each power message (consider reducing)
	qOptions.cbMaxMessage = dwPowerMsgSize;
	qOptions.bReadAccess = TRUE;
	
	// Create a message queue to receive power notifications
	waitFor[WAIT_POWER_INDEX] = CreateMsgQueue(_T("JetPwrMonitor"), &qOptions);
#ifdef DEBUG
	sprintf(szLog, "CreateMsgQueue for power returned %8.8X, Error=%8.8X", waitFor[WAIT_POWER_INDEX], GetLastError());
	LogString(szLog, DEBUG_POWER);
#endif
	if (waitFor[WAIT_POWER_INDEX] == NULL)
	{
		waitFor[WAIT_POWER_INDEX] = hPowerDummyEvent;
	}

	wcscpy(szBtDevName, _T("BTL1:")); // Default to MS implemented device name
	if (ps != PwrDeviceUnspecified)
	{
		HKEY    hKey                = NULL;
		DWORD   dwType              = 0;
		DWORD	dwSize				= 0;
		TCHAR	szPath[32];
		LONG	lRet				= 0;

		lRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Bluetooth\\Transports\\BuiltIn"), 0, 0, &hKey);
		if (ERROR_SUCCESS == lRet)
		{
			// enumerate first subkey - just stop there because that should have the driver in active use
			dwSize = sizeof(szPath)/sizeof(TCHAR);
			if (ERROR_SUCCESS == RegEnumKeyEx(hKey, 0, szPath, &dwSize, NULL, NULL, NULL, NULL))
			{
				HKEY hSubKey = NULL;
				if (ERROR_SUCCESS == RegOpenKeyEx(hKey, szPath, 0, 0, &hSubKey))
				{
					dwSize = sizeof(szPath);
					dwType = 0;
					lRet = RegQueryValueEx(hSubKey, _T("Name"), NULL, &dwType, (LPBYTE) &szPath, &dwSize);
					if ((lRet == ERROR_SUCCESS) && dwSize && (dwType == REG_SZ))
					{
						wcscpy(szBtDevName, szPath);
					}
					RegCloseKey(hSubKey);
				}
			}
			RegCloseKey(hKey);
		}

	}
#ifdef DEBUG
	sprintf(szLog, "Monitor thread started, Initial wait=%i", dwWaitTime);
	LogString(szLog, DEBUG_INFO);
#endif
	while (TRUE)
	{
		BOOL bLocalReportStatus;
#ifdef USE_NOTIFY
		if (g_pNotify != NULL)
			dwWaitTime = INFINITE;
#endif
#ifdef DEBUG
		sprintf(szLog, "Monitor thread waiting %i on %i objects, Time since ping=%i", dwWaitTime, 4, dwTimeSincePingedHF);
		LogString(szLog, DEBUG_THREADING);
		sprintf(szLog, "Waiting on %8.8X, %8.8X, %8.8X, and %8.8X", waitFor[0], waitFor[1], waitFor[2], waitFor[3]);
		LogString(szLog, DEBUG_POWER);
#endif
		DWORD dwRet = WaitForMultipleObjects(4, waitFor, FALSE, dwWaitTime);
#ifdef DEBUG
		sprintf(szLog, "Wait returned %i, Error=%8.8X", dwRet, GetLastError());
		LogString(szLog, DEBUG_POWER);
#endif
		EnterCriticalSection(&csStatus);
		bLocalReportStatus = g_bReportStatus;
		LeaveCriticalSection(&csStatus);
		if (dwRet == WAIT_OBJECT_0) // CleanupSession was called, we are being unloaded
		{
#ifdef DEBUG
			strcpy(szLog, "MonitorThread exiting");
			LogString(szLog, DEBUG_TAPI);
#endif
			EmptyResponseQueues();
			if (hPowerReq != 0)
			{
				ReleasePowerRequirement(hPowerReq);
				hPowerReq = 0;
			}
			break;
		}
		else if (dwRet == WAIT_OBJECT_0+1) // Call state changed
		{
			if (!bInSession)
			{
				dwWaitTime = INFINITE;
				if (hPowerReq != 0)
				{
					ReleasePowerRequirement(hPowerReq);
					hPowerReq = 0;
				}
				if (waitFor[WAIT_POWER_INDEX] != hPowerDummyEvent) // Power notification queue
				{
					CloseMsgQueue(waitFor[WAIT_POWER_INDEX]);
					waitFor[WAIT_POWER_INDEX] = hPowerDummyEvent;
				}
				if (hPowerNotifications != NULL)
				{
					StopPowerNotifications(hPowerNotifications);
					hPowerNotifications = NULL;
				}
			}
			else if (g_dwNumVoiceCalls > 0 && bLocalReportStatus)
				dwWaitTime = g_dwInCallInterval; // time out every 2 secconds (default) to update signal strength
			else if (bLocalReportStatus)
				dwWaitTime = g_dwIdleInterval; // time out every 5 seconds (default) to update signal strength
			else
				dwWaitTime = 60000; // if we happen to have a call state change when not monitoring status, set 60 sec timeout to verify we can still talk to HF
			if (bInSession)
			{
				if ((hPowerReq == 0) && (ps != PwrDeviceUnspecified))
				{
					hPowerReq = SetPowerRequirement(szBtDevName, ps, POWER_NAME | POWER_FORCE, NULL, 0);
				}
				if (waitFor[WAIT_POWER_INDEX] == hPowerDummyEvent)
				{
					qOptions.dwMaxMessages = 4; // This may be limiting number of occurances of each power message (consider reducing)
					qOptions.cbMaxMessage = dwPowerMsgSize;
					// Create a message queue to receive power notifications
					waitFor[WAIT_POWER_INDEX] = CreateMsgQueue(_T("JetPwrMonitor"), &qOptions);
#ifdef DEBUG
					sprintf(szLog, "CreateMsgQueue for power returned %8.8X, Error=%8.8X", waitFor[WAIT_POWER_INDEX], GetLastError());
					LogString(szLog, DEBUG_POWER);
#endif
					if (waitFor[WAIT_POWER_INDEX] != NULL)
					{
						// Request power notifications 
						hPowerNotifications = RequestPowerNotifications(waitFor[WAIT_POWER_INDEX], PBT_TRANSITION | PBT_POWERINFOCHANGE);
#ifdef DEBUG
						if (hPowerNotifications == NULL) 
						{
							sprintf(szLog, "RequestPowerNotifications failed, error %8.8X", GetLastError());
							LogString(szLog, DEBUG_POWER);
						}
#endif
					}
					else
						waitFor[WAIT_POWER_INDEX] = hPowerDummyEvent;
				}
			}

#ifdef DEBUG
			sprintf(szLog, "CallState signaled: Wait=%i, Dev=%ls, Power state=%i-%8.8X", dwWaitTime, szBtDevName, ps, hPowerReq);
			LogString(szLog, DEBUG_INFO);
#endif
		}
		else if (dwRet == WAIT_OBJECT_0+WAIT_POWER_INDEX) // Power notification
		{
			DWORD cbRead;
			DWORD dwFlags;
			POWER_BROADCAST *ppb = (POWER_BROADCAST*) new BYTE[dwPowerMsgSize];
	            
			while (ReadMsgQueue(waitFor[WAIT_POWER_INDEX], ppb, dwPowerMsgSize, &cbRead, 0, &dwFlags))
			{
#ifdef DEBUG
				sprintf(szLog, "ReadMsgQueue got Message %i and flags %8.8X. InSession=%i", ppb->Message, ppb->Flags, bInSession);
				LogString(szLog, DEBUG_POWER);
#endif
				if (bInSession)
				{
					switch (ppb->Message)
					{
						case PBT_TRANSITION:
#ifdef DEBUG
							sprintf(szLog, "Power notify-TRANSITION: %8.8X, %i, %S", ppb->Flags, ppb->Length, (ppb->Length) ? ppb->SystemPowerState : NULL);
							LogString(szLog, DEBUG_POWER);
#endif
							if (!(ppb->Flags & POWER_STATE_ON) && (g_dwFeatureMask & FM_FORCE_POWER_ON) && bInSession)
							{
								if ((ppb->Length) && (wcsicmp(g_szPowerResumeName, ppb->SystemPowerState) != 0))
									ResumeDevice();
							}
							break;

						case PBT_RESUME:
#ifdef DEBUG
							LogString("Power notify-RESUME", DEBUG_POWER);
#endif
							break;

						case PBT_POWERINFOCHANGE:
						{

							// PBT_POWERINFOCHANGE message embeds a 
							// POWER_BROADCAST_POWER_INFO structure into the 
							// SystemPowerState field
							PPOWER_BROADCAST_POWER_INFO ppbpi = (PPOWER_BROADCAST_POWER_INFO) ppb->SystemPowerState;
							if (ppbpi) 
							{
#ifdef DEBUG
								sprintf(szLog, "Power notify-POWERINFOCHANGE: Battery at %i", ppbpi->bBatteryLifePercent);
								LogString(szLog, DEBUG_POWER);
#endif
								// RETAILMSG(1,(L"ACLineStatus = %d\n",ppbpi->bACLineStatus));
								// RETAILMSG(1,(L"BatteryFlag = %d\n",ppbpi->bBatteryFlag));
								DWORD dwBattLevel = (ppbpi->bBatteryLifePercent < 90) ? ((ppbpi->bBatteryLifePercent + 10) / 20) : 5;
								DWORD dwLastReportBattLevel = (g_dwBatteryLevel < 90) ? ((g_dwBatteryLevel + 10) / 20) : 5;
								EnterCriticalSection(&csStatus);
								g_dwBatteryLevel = ppbpi->bBatteryLifePercent;
								LeaveCriticalSection(&csStatus);
								if (dwBattLevel != dwLastReportBattLevel)
								{
									if (g_bReportStatus)
									{
										CHAR szReply[20];
										sprintf(szReply, "+CIEV: 4,%i", dwBattLevel);
										PostResponse(szReply);
									}
								}
							}
							break;
						}
						default:
							break;
					}
				}
			}
#ifdef DEBUG
			// changed from if/else to while, so this ReadMsgQueue should always fail after last message in queue is read
			{
				sprintf(szLog, "ReadMsgQueue %8.8X failed, error=%8.8X", waitFor[2], GetLastError());
				LogString(szLog, DEBUG_POWER);
			}
#endif
			delete[] ppb;
		}
#ifdef USE_NOTIFY
		else if (dwRet == WAIT_OBJECT_0+WAIT_NOTIFY_INDEX) // Notification from notification broker
		{
			if (g_pNotify != NULL)
			{
				g_pNotify->ProcessMessages();
			}
		}
#endif
		else if (dwRet == WAIT_TIMEOUT)
		{
			dwTimeSincePingedHF +=dwWaitTime;
			if (dwTimeSincePingedHF >= 60000) // has it been at least 1 min since last ping
			{
#ifdef DEBUG
				sprintf(szLog, "Monitor thread timeout %i, Time since ping=%i", dwWaitTime, dwTimeSincePingedHF);
				LogString(szLog, DEBUG_THREADING);
#endif
				if (bInSession)
				{
#ifdef USE_NOTIFY
					if (g_pNotify == NULL) // only check for messages if notifications aren't active
#endif
					CheckForNewMessages();
					// leave dwWaitTime same as before timeout
				}
				else
					dwWaitTime = INFINITE;
				/* FIX: 1.22 - Use thread ID to track session start/stop instead of HF device control ping
				else
				{
#ifdef DEBUG
					strcpy(szLog, "HF disconnected-Waiting for signal");
					LogString(szLog, DEBUG_TAPI);
#endif
					EnterCriticalSection(&csStatus);
					bInSession = FALSE;
					g_bReportStatus = FALSE;
					g_bReportCID=FALSE;
					g_bReportECID=FALSE;
					g_dwReportCIDName=FALSE;
					g_bNeedExtraStatus = FALSE;
					g_wConvertToCharSet = CHARSET_GSM;
					g_bDialingInitiated = FALSE;
					g_bAnsweringCall = FALSE;
					LeaveCriticalSection(&csStatus);
					ResetContacts();
					CleanupMAPI();
					EmptyResponseQueues(); // Replace these with turning off Notify
					// SetMapiNotify(MESSAGES_ME, 0, 0);
					// SetMapiNotify(MESSAGES_SM, 0, 0);
					// CleanupTAPI(); //TODO: Testing to see if this will eliminate occasional mystery call status
					dwWaitTime = INFINITE;
					// g_szHFDevName[0] = 0; // FIX: 1.22
					if (hPowerReq != 0)
					{
						ReleasePowerRequirement(hPowerReq);
						hPowerReq = 0;
					}
				}
				*/
				dwTimeSincePingedHF = 0;
			}
		}
		if (bLocalReportStatus && bInSession)
			UpdateIndicators();

		ProcessResponses();
	}
#ifdef USE_NOTIFY
	if (waitFor[WAIT_NOTIFY_INDEX] != hNotifyDummyEvent) // State and Notification broker queue
	{
		CloseMsgQueue(waitFor[WAIT_NOTIFY_INDEX]);
		waitFor[WAIT_NOTIFY_INDEX] = NULL;
	}
#endif
	if (waitFor[WAIT_POWER_INDEX] != hPowerDummyEvent) // Power notification queue
	{
		CloseMsgQueue(waitFor[WAIT_POWER_INDEX]);
		waitFor[WAIT_POWER_INDEX] = NULL;
	}
	if (hPowerNotifications != NULL)
	{
		StopPowerNotifications(hPowerNotifications);
		hPowerNotifications = NULL;
	}
	CloseHandle(hKillMonitor);
	hKillMonitor = NULL;
	CloseHandle(hCallState);
	hCallState = NULL;
	CloseHandle(hPowerDummyEvent);
	hPowerDummyEvent = NULL;
	CloseHandle(hNotifyDummyEvent);
	hNotifyDummyEvent = NULL;
	return 0;
}

void InitializeSession(BOOL bStartSession)
{
#ifdef DEBUG
	CHAR szLog[128];
#endif
	if (!bInSession)
	{
		if (hBAG == INVALID_HANDLE_VALUE)
			hBAG = CreateFile(L"BAG0:",0,0,NULL,OPEN_EXISTING,0,NULL);
		// start timer event to disconnect when we can't talk to the AG anymore
		if (hMonitorThread != NULL)
		{
			DWORD dwExitCode = 0;
			// Verify thread ID is still valid
			if (!GetExitCodeThread(hMonitorThread, &dwExitCode))
			{
#ifdef DEBUG
				sprintf(szLog, "Thread handle no longer valid, error %8.8X", GetLastError());
				LogString(szLog, DEBUG_THREADING);
#endif
				hMonitorThread = NULL;
			}
		}
		if (hMonitorThread == NULL)
			hMonitorThread = CreateThread(NULL, 0, MonitorThreadProc, NULL, 0, NULL);
		if (hMonitorThread != NULL)
		{
			if (bStartSession)
			{
				bInSession = TRUE;
			}
			if (hKillMonitor == NULL)
				hKillMonitor = CreateEvent(NULL, FALSE, FALSE, NULL);
			if (hCallState == NULL)
				hCallState = CreateEvent(NULL, FALSE, FALSE, NULL);
			if (hPowerDummyEvent == NULL)
				hPowerDummyEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
			if (hNotifyDummyEvent == NULL)
				hNotifyDummyEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
#ifdef DEBUG
			sprintf(szLog, "InitializeSession: hCallState = %8.8X", hCallState);
			LogString(szLog, DEBUG_THREADING);
#endif
		}
	}
	InitializeTAPI();

	/*
	if (g_szHFDevName[0] == 0 || g_bDeviceChanged)
	{
		// TODO: Move Messaging cleanup into this section allowing reported messages to persist across sessions
		//		Requires more robust memory management in messaging lists - must limit to reported slots and recycle slots
	}
	*/
	{
		//TODO: Use notifications to cleanup on actual HF disconnect event
		EnterCriticalSection(&csStatus);
		// g_bReportStatus = FALSE;
		g_bReportCID=FALSE;
		g_bReportECID=FALSE;
		// FIX: 1.20 RTW: On Nokia HS-12W CID names don't work because g_dwReportCIDName was always reset after read from registry
		// g_dwReportCIDName=FALSE; // Don't need to reset this because it is reset in GetRegSettings in all cases
		g_bNeedExtraStatus = FALSE;
		g_bDialingInitiated = FALSE;
		g_bAnsweringCall = FALSE;
		// g_bDeviceChanged = FALSE;
		LeaveCriticalSection(&csStatus);
		CleanupMAPI();
		EmptyResponseQueues();
		ResetContacts();
	}

#ifdef DEBUG
#ifdef USE_NOTIFY
	sprintf(szLog, "InitializeSession: Report Status = %i, notify = %8.8X", g_bReportStatus, g_pNotify);
#else
	sprintf(szLog, "InitializeSession: Report Status = %i, notify not used", g_bReportStatus);
#endif
	LogString(szLog, DEBUG_THREADING);
#endif
	EnterCriticalSection(&csStatus);
	bInSession = bStartSession;
	LeaveCriticalSection(&csStatus);
	if (g_bReportStatus && (hCallState != NULL))
		SetEvent(hCallState);
	if (bInSession)
	{
		BOOL bRet = FALSE;
		if (hBAG != INVALID_HANDLE_VALUE && !(g_dwFeatureMask & FM_NOPING_HF_CTRL))
		{
			bRet = DeviceIoControl(hBAG,IOCTL_AG_OPEN_CONTROL,NULL,0,NULL,0,NULL,NULL);
		}
		if (g_dwFeatureMask & FM_FORCE_POWER_ON)
		{
			ResumeDevice();
		}
		if (g_dwFeatureMask & FM_HI_PRI_THREADS)
		{
			HANDLE hMainThread = GetCurrentThread();
			SetThreadPriority(hMainThread, THREAD_PRIORITY_HIGHEST);
			SetThreadPriority(hMonitorThread, THREAD_PRIORITY_HIGHEST);
		}
		GetBatteryLevel();
	}
}

void KickMonitorThread()
{
	if (g_bReportStatus && (hCallState != NULL))
		SetEvent(hCallState);
}

void CleanupSessionState()
{
#ifdef DEBUG
	LogString("CleanupSessionState-Waiting for signal", DEBUG_TAPI);
#endif
	EnterCriticalSection(&csStatus);
	bInSession = FALSE;
	g_bReportStatus = FALSE;
	g_bReportCID=FALSE;
	g_bReportECID=FALSE;
	g_dwReportCIDName=FALSE;
	g_bNeedExtraStatus = FALSE;
	g_wConvertToCharSet = CHARSET_GSM;
	g_bDialingInitiated = FALSE;
	g_bAnsweringCall = FALSE;
	LeaveCriticalSection(&csStatus);
	ResetContacts();
	g_dwPSSelected = CONTACTS_ME;
	g_dwMSSelected = MESSAGES_SM;
#ifdef USE_NOTIFY
	if (g_pNotify != NULL)
	{
		g_pNotify->StopIndicators();
		g_pNotify->StopMail(MESSAGES_ME);
		g_pNotify->StopMail(MESSAGES_SM);
#ifdef USE_NOTIFY_CID
		g_pNotify->StopCallerID(JET_REPORT_CLIP);
		g_pNotify->StopCallerID(JET_REPORT_ELIP);
#endif
	}
#endif
	CleanupMAPI();
	EmptyResponseQueues(); // Replace these with turning off Notify
	// SetMapiNotify(MESSAGES_ME, 0, 0);
	// SetMapiNotify(MESSAGES_SM, 0, 0);
	// CleanupTAPI(); //TODO: Testing to see if this will eliminate occasional mystery call status
	if (g_dwFeatureMask & FM_HI_PRI_THREADS)
	{
		HANDLE hMainThread = GetCurrentThread();
		SetThreadPriority(hMainThread, THREAD_PRIORITY_NORMAL);
		SetThreadPriority(hMonitorThread, THREAD_PRIORITY_NORMAL);
	}
	g_dwSessionThreadId = 0;
	SetEvent(hCallState); // this will trigger monitor thread to release power requirement and sleep
}

void CleanupSession()
{
	// bInSession = FALSE;
	CleanupSessionState();
#ifdef USE_NOTIFY
	if (g_pNotify != NULL)
	{
		delete g_pNotify;
		g_pNotify = NULL;
	}
#endif
	if (hBAG != INVALID_HANDLE_VALUE)
	{
		CloseHandle(hBAG);
		hBAG = INVALID_HANDLE_VALUE;
	}
	if (hMonitorThread)
	{
		SetEvent(hKillMonitor);
		WaitForSingleObject(hMonitorThread, 10000);
		CloseHandle(hMonitorThread);
		hMonitorThread = NULL;
		if (hKillMonitor != NULL)
		{
			CloseHandle(hKillMonitor);
			hKillMonitor = NULL;
		}
		if (hCallState != NULL)
		{
			CloseHandle(hCallState);
			hCallState = NULL;
		}
		if (hPowerDummyEvent != NULL)
		{
			CloseHandle(hPowerDummyEvent);
			hPowerDummyEvent = NULL;
		}
		if (hNotifyDummyEvent != NULL)
		{
			CloseHandle(hNotifyDummyEvent);
			hNotifyDummyEvent = NULL;
		}
	}
}

void ReportCallSetup()
{
	CHAR szReply[20];

	EnterCriticalSection(&csStatus);
	if (g_bNeedExtraStatus && g_bReportStatus && !g_bInCall)
	{
		sprintf(szReply, "+CIEV: 3,%i", g_dwCallSetup);
		if (g_dwCallSetup == CALLSETUP_OUTRING)
			PostResponse("+CIEV: 3,2"); // send CALLSETUP_OUTGOING before CALLSETUP_OUTRING
		PostResponse(szReply);
	}
	LeaveCriticalSection(&csStatus);
}

/***********************************************************************
	  
FUNCTION: 
lineCallbackFunc

PURPOSE: 
This is a callback function invoked to determine status and events on
the line device, addresses, or calls. 
		  
***********************************************************************/
VOID CALLBACK lineCallbackFunc (
    DWORD hDevice, 
    DWORD dwMsg, 
    DWORD dwCallbackInstance,
    DWORD dwParam1, 
    DWORD dwParam2, 
    DWORD dwParam3
    )
{
		  
	BOOL bCloseLine = FALSE;
	LPTSTR lpszStatus;
	lpszStatus = TEXT(" ");
#ifdef DEBUG
	CHAR szReport[64];
	sprintf(szReport, "lineCallbackFunc msg %8.8X-%8.8X-%8.8X : %8.8X", dwMsg, dwParam1, dwParam2, hDevice);
	LogString(szReport, DEBUG_TAPI);
#endif
#ifdef USE_NOTIFY_TAPI
	if (g_pNotify != NULL)
		return;
#endif
	switch (dwMsg)
	{
		case LINE_APPNEWCALL:
		{
			AddCall((HCALL) dwParam2);
			if (g_bDialingInitiated)
			{
				g_bDialingInitiated = FALSE;
				SetCallState((HCALL) dwParam2, CALL_OUTGOING);
			}
			break;
		}
		case LINE_CALLINFO:
		{ // switch isn't completely correct here since dwParam1 may be multiple bits OR'd together
			switch (dwParam1)
			{
#ifndef USE_NOTIFY_CID
				case LINECALLINFOSTATE_CALLERID:
				{
#ifdef DEBUG
					sprintf(szReport, "CALLERID message, report=%i,%i", g_bReportCID, g_bReportECID);
					LogString(szReport, DEBUG_CALLERID);
#endif
					// *ELIP: "Name"
					// +CLIP: "14259876543",129,,,"Name"
					if (g_bReportCID || g_bReportECID) // && IsVoiceCall((HCALL) hDevice)) // NOTE: incoming non-voice calls with CID info aren't likely to show up - if they do, report CID anyway because CID info often comes before Media Change
					{
						DWORD dwSize = sizeof(LINECALLINFO) + 512;
						LPLINECALLINFO pInfo = NULL;
						while (pInfo == NULL)
						{
							pInfo = (LPLINECALLINFO) LocalAlloc(LPTR, dwSize);
							if (pInfo == NULL)
							{
#ifdef DEBUG
								strcpy(szReport, "Memory error in lineGetCallInfo");
								LogString(szReport, DEBUG_CALLERID);
#endif
								break;
							}
							pInfo->dwTotalSize = dwSize;
							DWORD dwRet = lineGetCallInfo((HCALL) hDevice, pInfo);
							if (dwRet == LINEERR_STRUCTURETOOSMALL)
							{
								dwSize = pInfo->dwNeededSize;
								LocalFree(pInfo);
								pInfo = NULL;
							}
							else if (dwRet != 0) // any other failure
							{
#ifdef DEBUG
								sprintf(szReport, "Error %8.8X in lineGetCallInfo - Mode=%i", dwRet, pInfo->dwMediaMode);
								LogString(szReport, DEBUG_CALLERID);
#endif
								LocalFree(pInfo);
								pInfo = NULL;
								break;
							}
						}
						if (pInfo != NULL)
						{
							//TODO: Allocate buffers dynamically
							TCHAR szName[128];
							TCHAR szUnavail[16];
							LPTSTR lpNumber = (LPTSTR)((LPSTR)((DWORD)pInfo+(DWORD)pInfo->dwCallerIDOffset));
							LPTSTR lpName = szName;
							DWORD dwType = 129;
							BOOL  bHaveInfo = FALSE;
							memset(szName, 0, sizeof(szName));
							
							if (pInfo->dwCallerIDSize > 0)
							{
								if (*lpNumber == '+')
									dwType = 145;
								else if (*lpNumber == '1')
									dwType = 161;
							}
							// szReply[0] = 0;
							if (pInfo->dwCallerIDFlags & LINECALLPARTYID_NAME)
							{
								//TODO: This should be mapped to selected charset
								/*
								sprintf(szReply, "+CLIP: \"%ls\",%i,,,\"%ls\"", 
									lpNumber, dwType, lpName);
								*/
								bHaveInfo = TRUE;
								lpName = (LPTSTR)((LPSTR)((DWORD)pInfo+(DWORD)pInfo->dwCallerIDNameOffset));
							}
							else if (pInfo->dwCallerIDFlags & (LINECALLPARTYID_ADDRESS | LINECALLPARTYID_PARTIAL))
							{
								/*
								CHAR szName[1024];
								if (FindContact(lpNumber, szName))
									sprintf(szReply, "+CLIP: \"%ls\",%i,,,\"%s\"", lpNumber, dwType, szName);
								else
									sprintf(szReply, "+CLIP: \"%ls\",%i", lpNumber, dwType);
								*/
								bHaveInfo = TRUE;
								FindContact(lpNumber, szName);
							}
							else if (pInfo->dwCalledIDFlags & (LINECALLPARTYID_BLOCKED | LINECALLPARTYID_OUTOFAREA 
								| LINECALLPARTYID_UNAVAIL | LINECALLPARTYID_UNKNOWN))
							{
								// CHAR szReason[16];
								bHaveInfo = TRUE;
								lpNumber = szUnavail;
								lstrcpy(szUnavail, _T("0000000"));
								if (pInfo->dwCalledIDFlags & LINECALLPARTYID_BLOCKED)
									lstrcpy(szName, _T("BLOCKED"));
								else if (pInfo->dwCalledIDFlags & LINECALLPARTYID_OUTOFAREA)
									lstrcpy(szName, _T("Out of Area"));
								else if (pInfo->dwCalledIDFlags & LINECALLPARTYID_UNAVAIL)
									lstrcpy(szName, _T("Unavailable"));
								else if (pInfo->dwCalledIDFlags & LINECALLPARTYID_UNKNOWN)
									lstrcpy(szName, _T("Unknown"));

								// sprintf(szReply, "+CLIP: \"0000000000\",129,,,\"%s\"", szReason);
#ifdef DEBUG
								sprintf(szReport, "%ls - dwCallerIDFlags = %i", szName, pInfo->dwCallerIDFlags);
								LogString(szReport, DEBUG_CALLERID);
#endif
							}
							if (bHaveInfo)
							{
								CHAR szReply[1024];
								CHAR  szMappedName[512];
								// CHAR  szMappedNumber[256];
								// szMappedNumber[0] = 0;
								szMappedName[0] = 0;
								// if ((lpNumber != NULL) && (*lpNumber != 0))
								// 	ConvertString(szMappedNumber, lpNumber, sizeof(szMappedNumber));
								if ((lpName != NULL) && (*lpName != 0))
									ConvertString(szMappedName, lpName, sizeof(szMappedName));
								// PostResponse("\r\nRING");
								PostResponse("RING"); //TEST: Does the extra crlf cause Jabra to shut off?
								// AddLineToResponse("RING\r\n", FALSE);
								if (g_bReportECID)
								{
									if (szMappedName[0] != 0)
										sprintf(szReply, "*ELIP: \"%s\"", szMappedName);
									// AddLineToResponse(szReply, FALSE);
									PostResponse(szReply);
								}
								// szMappedName[0] = 0;
								if (g_bReportCID)
								{
									if ((g_dwReportCIDName) && (szMappedName[0] != 0))
										sprintf(szReply, "+CLIP: \"%s\",%i", szMappedName, dwType);
									else if (szMappedName[0] != 0)
										sprintf(szReply, "+CLIP: \"%ls\",%i,,,\"%s\"", lpNumber, dwType, szMappedName);
									else
										sprintf(szReply, "+CLIP: \"%ls\",%i", lpNumber, dwType);
									PostResponse(szReply);
									// AddLineToResponse(szReply, TRUE);
									InitializeResponse();
								}
							}
							LocalFree(pInfo);
						}
					}
					break;
				}
#endif
				case LINECALLINFOSTATE_MEDIAMODE:
				{
					if (IsVoiceCall((HCALL) hDevice))
					{
						if (g_dwCallSetup != CALLSETUP_INCOMING)
							g_dwCallSetup = CALLSETUP_OUTRING;
						CallIsVoice((HCALL) hDevice);
						ReportCallSetup();
						if (g_dwCallSetup == CALLSETUP_OUTRING)
						{
							SetCallState((HCALL) hDevice, LINECALLSTATE_DIALING);
						}
						// ConnectAudio();
					}
#ifdef DEBUG
					sprintf(szReport, "Media mode change - media=%8.8X, calls=%i/%i", hDevice, g_dwNumCalls, g_dwNumVoiceCalls);
					LogString(szReport, DEBUG_TAPI);
#endif
					break;
				}
				default:
					break;
			}
			break;
		}

		case LINE_CALLSTATE:  // Sent after change of call state
		{
			DWORD dwReturn = 0;
			switch (dwParam1)
			// switch isn't completely correct here since dwParam1 may be multiple bits OR'd together
			{
				// problem using LINE_CALLSTATE for outgoing call setup since these will pop (briefly) for data calls too
				case LINECALLSTATE_DIALING:
				case LINECALLSTATE_PROCEEDING:
				{
					g_dwCallSetup = CALLSETUP_OUTGOING;
					SetCallState((HCALL) hDevice, dwParam1 | CALL_OUTGOING);
					ReportCallSetup();
					break;
				}
				case LINECALLSTATE_RINGBACK:
				{
					g_dwCallSetup = CALLSETUP_OUTRING;
					SetCallState((HCALL) hDevice, dwParam1 | CALL_OUTGOING);
					ReportCallSetup();
					break;
				}
				case LINECALLSTATE_ONHOLD:
				case LINECALLSTATE_CONFERENCED:
				case LINECALLSTATE_ONHOLDPENDCONF:
				case LINECALLSTATE_ONHOLDPENDTRANSFER:
				{
					SetCallState((HCALL) hDevice, dwParam1);
					break;
				}
				case LINECALLSTATE_OFFERING:
				{
					g_dwCallSetup = CALLSETUP_INCOMING;
					SetCallState((HCALL) hDevice, dwParam1);
					ReportCallSetup();
					if (g_dwInBandRing && bInSession)
					{
#ifdef DEBUG
						LogString("Incoming call - Opening audio", DEBUG_INBANDRING);
#endif
						ConnectAudio();
					}
					break;
				}
				case LINECALLSTATE_BUSY:
				{
					g_dwCallSetup = CALLSETUP_NONE;
					ReportCallSetup();
					break;
				}
				case LINECALLSTATE_DISCONNECTED:
				{
					g_dwCallSetup = CALLSETUP_NONE;
					g_bAnsweringCall = FALSE;
					dwReturn = DeleteCall((HCALL) hDevice);
#ifdef DEBUG
					sprintf(szReport, "Call disconnected - return=%i, calls=%i/%i", dwReturn, g_dwNumCalls, g_dwNumVoiceCalls);
					LogString(szReport, DEBUG_TAPI);
#endif
					ReportCallSetup();
					if (bInSession && (g_dwNumVoiceCalls == 0))
					{
						if (g_dwSleepOnDisconnect)
						{
#ifdef DEBUG
							LogString("Setting power to unattended", DEBUG_INFO);
#endif
							SuspendDevice();
						}
					}
					break;
				}
				case LINECALLSTATE_CONNECTED:
				{
					if (/*g_dwInBandRing &&*/ bInSession && (g_dwFeatureMask & FM_RESET_AUDIO))
					{
#ifdef DEBUG
						LogString("Incoming call connected - Reconnecting audio", DEBUG_INBANDRING);
#endif
						DisconnectAudio();
						ConnectAudio();
					}

					EnterCriticalSection(&csStatus);
					g_dwCallSetup = CALLSETUP_NONE;
					g_bAnsweringCall = FALSE;
					if (!IsVoiceCall((HCALL) hDevice))
						// we don't need the call handle or more messages for data calls, so we will deallocate it now.
						DeleteCall((HCALL) hDevice);
					else
					{
						SetCallState((HCALL) hDevice, dwParam1);
						if (g_bNeedExtraStatus && g_bReportStatus && !g_bInCall)
						{
							PostResponse("+CIEV: 2,1");
							ReportCallSetup();
						}
						g_bInCall = TRUE;
						// ConnectAudio();
					}
					LeaveCriticalSection(&csStatus);
#ifdef DEBUG
					sprintf(szReport, "New call connected - hCall=%8.8X, calls=%i/%i", hDevice, g_dwNumCalls, g_dwNumVoiceCalls);
					LogString(szReport, DEBUG_TAPI);
#endif

					break;
				}
				default:
					break;
			}
			break;
		}
		case LINE_DEVSPECIFIC:
		{
#ifdef USE_NOTIFY
			break;
#endif
			switch (dwParam1)
			{
				case LINE_GPRSREGISTERSTATE:
				case LINE_REGISTERSTATE:
				{
#ifdef DEBUG
					CHAR szLog[48];
					sprintf(szLog, "LINE_REGISTERSTATE = %8.8X", dwParam2);
					LogString(szLog, DEBUG_TAPI);
#endif
					BOOL bService = (dwParam2 > LINEREGSTATUS_ATTEMPTING) ? TRUE : FALSE;
					EnterCriticalSection(&csStatus);
					g_bRoaming = (dwParam2 == LINEREGSTATUS_ROAM) ? TRUE : FALSE;
					g_bRoaming = (g_dwInvertRoam) ? !g_bRoaming : g_bRoaming;

					if (g_bReportStatus)
					{
						CHAR szReply[20];
						// To support Operator info, send +CIEV: (roam),x always
						// to cover case when switching from one roam operator to another
						sprintf(szReply, "+CIEV: 6,%i", g_bRoaming);
						PostResponse(szReply);
						if (g_bNeedExtraStatus && (bService != g_bService))
						{
							// we need to report service indicator
							sprintf(szReply, "+CIEV: 1,%i", bService);
							PostResponse(szReply);
						}
						g_bService = bService;
					}
					LeaveCriticalSection(&csStatus);
#ifdef DEBUG
					if (g_bService)
					{
						/*
						TCHAR szOperator[MAX_LENGTH_OPERATOR_LONG+2] = {0};
						if (GetOperator(szOperator))
							sprintf(szLog, "Operator is %S", szOperator);
						else
							strcpy(szLog, "FAILED GetOperator");
							*/
						strcpy(szLog, "Registered operator changed.");
						LogString(szLog, DEBUG_INFO);
					}
#endif
					break;
				}
				default:
					break;
			}
			break;
		}

//		case LINE_LINEDEVSTATE:
//		case LINE_CREATE:
//		case LINE_CLOSE:
//		case LINE_ADDRESSSTATE:
//		case LINE_DEVSPECIFICFEATURE:
//		case LINE_GATHERDIGITS:
//		case LINE_GENERATE:
//		case LINE_MONITORDIGITS:
//		case LINE_MONITORMEDIA:
//		case LINE_MONITORTONE:
//		case LINE_REMOVE:
//		case LINE_REQUEST:
		default:
			break;
	}
}

#define CLCC_OUTGOING 0
#define CLCC_INCOMING 1
#define CLCC_STAT_ACTIVE	0
#define CLCC_STAT_ON_HOLD	1
#define CLCC_STAT_DIALING	2
#define CLCC_STAT_OUTRING	3
#define CLCC_STAT_INRING	4

// response to AT+CLCC request
// +CLCC : <id1>, <dir(0=out,1=in)>, <stat>, <mode(0=voice)>, <mpty(0=FALSE)> [ ,<number>, <type>[<alpha> ] ][ <CR><LF>
// <stat (0=active,1=held,2=dialing out,3=ringing out,4=incoming,5=waiting in)>
// TODO: Use phone ID from callInfo if available
void OutputCurrentCallList()
{
	int nIndex = 1;
	int nDir = CLCC_INCOMING; // default to incoming
	int nStat = CLCC_STAT_ACTIVE; // default to active
	int nMode = 0; // default to voice
	int nMultiParty = 0; // default to single party
	LPCALLLIST pCurr = g_CallList;
	CHAR szReply[32];

	// Handle case where +CLCC is called immediately after ATD before call actually initiates
	if (g_bDialingInitiated && (g_dwFeatureMask & FM_CLCC_ATD_FAKE))
	{
		strcpy(szReply, "+CLCC: 1,0,2,0,0");
		AddLineToResponse(szReply, FALSE);
		nIndex++;
	}
#ifdef USE_NOTIFY
	if ((g_pNotify != NULL) && (g_dwFeatureMask & FM_CLCC_NOTIFY))
	{
		WORD wCalls = g_pNotify->CallCount();
		if (nIndex > 1) // We added the fake call to identify the newly placed outgoing call
			wCalls++;
		BOOL bReportedActive = FALSE;
		BOOL bReportedRinging = FALSE;
		BOOL bReportedDialing = FALSE;
		BOOL bReportedHold = FALSE;
		while(nIndex <= wCalls)
		{
			nDir = CLCC_INCOMING; // For now, default to incoming TODO: compare talking name to last incoming name
			nMultiParty = FALSE;
			if (g_pNotify->InCall() && !bReportedActive)
			{
				nStat = CLCC_STAT_ACTIVE;
				bReportedActive = TRUE;
				nMultiParty = g_pNotify->InConference();
			}
			else if (g_pNotify->Ringing() && !bReportedRinging)
			{
				nStat = CLCC_STAT_INRING;
				bReportedRinging = TRUE;
			}
			else if (g_pNotify->Dialing() && !bReportedDialing)
			{
				nDir = CLCC_OUTGOING;
				nStat = CLCC_STAT_DIALING; // or OUTRING
				bReportedDialing = TRUE;
			}
			else if (g_pNotify->OnHold() && !bReportedHold)
			{
				nStat = CLCC_STAT_ON_HOLD;
				bReportedHold = TRUE;
				nMultiParty = g_pNotify->InConference();
			}
			else // must have multiple active calls in conference
			{
				if (g_pNotify->InConference())
				{
					nStat = CLCC_STAT_ACTIVE;
					nMultiParty = TRUE;
				}
				else // unknown calls, report them as on hold
				{
					nStat = CLCC_STAT_ON_HOLD;
					nMultiParty = FALSE;
				}
			}

			sprintf(szReply, "+CLCC: %i,%i,%i,%i,%i", nIndex, nDir, nStat, nMode, nMultiParty);
			AddLineToResponse(szReply, FALSE);
			nIndex++;
		}
		return;
	}
#endif
	while (pCurr != NULL)
	{
		if (!(pCurr->IsVoice))
			nMode = 9; // unknown
		switch (LOWORD(pCurr->CallState))
		{
			case LINECALLSTATE_CONNECTED:
				break;
			case LINECALLSTATE_ONHOLD:
				nStat = CLCC_STAT_ON_HOLD;
				break;
			case LINECALLSTATE_DIALING:
				nDir = 0;
				nStat = CLCC_STAT_DIALING;
				break;
			case LINECALLSTATE_RINGBACK:
				nDir = 0;
				nStat = CLCC_STAT_OUTRING;
				break;
			case LINECALLSTATE_OFFERING:
				nStat = CLCC_STAT_INRING;
				break;
			default:
				if (pCurr->CallState & LINECALLSTATE_CONFERENCED)
				{
					nMultiParty = 1;
				}
				break;
		}
		if (pCurr->CallState & CALL_OUTGOING)
		{
			nDir = CLCC_OUTGOING;
		}
		sprintf(szReply, "+CLCC: %i,%i,%i,%i,%i", (g_dwFeatureMask & FM_CLCC_USEID) ? pCurr->ID : nIndex, nDir, nStat, nMode, nMultiParty);
		AddLineToResponse(szReply, FALSE);
		nIndex++;
		pCurr = pCurr->Next;
	}
}

// Put a placeholder call in the list when a call is dialed through the AG (ATDxxxxxx)
// so that an immediate call to AT+CLCC will return new call in dialing state
/*
void AddTempCall()
{
	AddCall(NULL);
	CallIsVoice(NULL);
	SetCallState(NULL, LINECALLSTATE_DIALING);
}
*/

/***********************************************************************
	  
FUNCTION: 
InitializeTAPI 

PURPOSE:  
Initialize the application's use of Tapi.dll.
		  
***********************************************************************/
DWORD InitializeTAPI ()
{
    DWORD dwLineID, dwReturn, dwNumDevs;
	DWORD dwAPIVersion = TAPI_CURRENT_VERSION;
	BOOL bFoundVoiceLine = FALSE;
	LPLINEDEVCAPS lpLineDevCaps = NULL;
#ifdef DEBUG
	CHAR szReply[64];
#endif

	if (g_hLineApp != NULL)
	{
		if (g_dwVoiceLineID != -1)
			bFoundVoiceLine = TRUE;
		// TODO: Handle unlikely case where g_hLineApp != NULL but g_dwVoiceLine == -1
	}
	else
	{
#ifdef DEBUG
		LogString("Initializing TAPI", DEBUG_TAPI);
#endif
		// Initialize the application's use of Tapi.dll.
		dwReturn = lineInitialize(&g_hLineApp, 
			(HINSTANCE) g_hInst, (LINECALLBACK) lineCallbackFunc, 
			g_szAppName, &dwNumDevs);

		// If function "lineInitialize" fails, then return.
		if (dwReturn)
		{
#ifdef DEBUG
			LogString("lineInitialize failed", DEBUG_TAPI);
#endif
			return dwReturn;
		}
    
		// If there is no device, then return.
		if (dwNumDevs == 0)
		{
#ifdef DEBUG
			LogString("There are no line devices available.", DEBUG_TAPI);
#endif
			return LINEERR_NODEVICE;
		}
    
		// Find first line supporting interactive voice
		for (dwLineID = 0; dwLineID < dwNumDevs; ++dwLineID)
		{
			DWORD dwSize;
			BOOL bVoiceLine = FALSE;
			LINEEXTENSIONID extID;

#ifdef DEBUG
			sprintf(szReply, "Calling lineNegotiateAPIVersion on line %i", dwLineID);
			LogString(szReply, DEBUG_TAPI);
#endif
    
			// Negotiate the API version number. If it fails, return to dwReturn.
			if (dwReturn = lineNegotiateAPIVersion (
				g_hLineApp,                 // TAPI registration handle
				dwLineID,                   // Line device to be queried
				TAPI_VERSION_2_0,           // Least recent API version 
				TAPI_CURRENT_VERSION,       // Most recent API version 
				&dwAPIVersion,				// Negotiated API version 
				&extID))                    // Must be NULL; the provider-
			{
#ifdef DEBUG
				sprintf(szReply, "lineNegotiateAPIVersion failure %8.8X", dwReturn);
				LogString(szReply, DEBUG_TAPI);
#endif
				continue;
			}
#ifdef DEBUG
			sprintf(szReply, "lineNegotiateAPIVersion %8.8X", dwAPIVersion);
			LogString(szReply, DEBUG_TAPI);
#endif
    
			dwSize = sizeof (LINEDEVCAPS);
    
			// Allocate enough memory for lpLineDevCaps.
			do
			{
				if (!(lpLineDevCaps = (LPLINEDEVCAPS) LocalAlloc (LPTR, dwSize)))
				{
#ifdef DEBUG
					LogString("Memory error in TAPI", DEBUG_TAPI);
#endif
					goto LineFailed;
				}
        
				lpLineDevCaps->dwTotalSize = dwSize;
        
				if (dwReturn = lineGetDevCaps(g_hLineApp, dwLineID,
					dwAPIVersion, 0, lpLineDevCaps))
				{
#ifdef DEBUG
					LogString("Couldn't get lineDevCaps", DEBUG_TAPI);
#endif
					goto LineFailed;
				}
        
				// Stop if the allocated memory is equal to or greater than the 
				// needed memory.
				if (lpLineDevCaps->dwNeededSize <= lpLineDevCaps->dwTotalSize)
					break;
				dwSize = lpLineDevCaps->dwNeededSize;
				LocalFree (lpLineDevCaps);
				lpLineDevCaps = NULL;
        
			} while (TRUE);
    
			bVoiceLine = (lpLineDevCaps->dwMediaModes & LINEMEDIAMODE_INTERACTIVEVOICE);
			if (bVoiceLine)
			{
				bFoundVoiceLine = TRUE;
				g_dwVoiceLineID = lpLineDevCaps->dwPermanentLineID;
				break;
			}

LineFailed:
#ifdef DEBUG
			LogString("This wasn't a voice line", DEBUG_TAPI);
#endif
			; // blank line after LineFailed label
		}
		if (lpLineDevCaps)
			LocalFree (lpLineDevCaps);
	}
	// if we found a voice line, open it and set it for notifications
	if (bFoundVoiceLine && (g_hVoiceLine == NULL))
	{
#ifdef DEBUG
		sprintf(szReply, "Found voice line ID: %i", g_dwVoiceLineID);
		LogString(szReply, DEBUG_TAPI);
#endif
		// lineOpen
		// Open the current line.
		if (dwReturn = lineOpen (
			g_hLineApp,                 // Usage handle for TAPI
			g_dwVoiceLineID,			// Cannot use the LINEMAPPER value
			&g_hVoiceLine,				// Line handle
			dwAPIVersion,				// API version number
			0,                          // Must set to zero for Windows CE
			0,                          // No data passed back 
			LINECALLPRIVILEGE_MONITOR,  // Can only make an outgoing call
			0,							// Media mode 
			NULL))                      // Must set to NULL for Windows CE
		{
#ifdef DEBUG
			LogString("Couldn't open voice line", DEBUG_TAPI);
#endif
			g_hVoiceLine = NULL;
			// return dwReturn;
		}
		else
		{
#ifdef USE_NOTIFY_TAPI
			if (g_pNotify != NULL)
				return ERR_NONE;
#endif
			DWORD dwSize = sizeof(LINECALLLIST) + (5 * sizeof(HCALL));
			LPLINECALLLIST pCallList = (LPLINECALLLIST) LocalAlloc(LPTR, dwSize);
#ifdef DEBUG
			LogString("Line opened", DEBUG_TAPI);
#endif
			if (pCallList != NULL)
			{
				pCallList->dwTotalSize = dwSize;
				dwReturn = lineGetNewCalls(g_hVoiceLine, 0, LINECALLSELECT_LINE, pCallList);
				if (dwReturn == LINEERR_STRUCTURETOOSMALL)
				{
					dwSize = pCallList->dwNeededSize;
					LocalFree(pCallList);
					pCallList = (LPLINECALLLIST) LocalAlloc(LPTR, dwSize);
					if (pCallList != NULL)
					{
						pCallList->dwTotalSize = dwSize;
						dwReturn = lineGetNewCalls(g_hVoiceLine, 0, LINECALLSELECT_LINE, pCallList);
					}
				}
				if (dwReturn == ERR_NONE)
				{
#ifdef DEBUG
					sprintf(szReply, "Found %i new calls starting %i from %8.8X", pCallList->dwCallsNumEntries,
						pCallList->dwCallsOffset, pCallList);
					LogString(szReply, DEBUG_TAPI);
#endif
					g_bInCall = FALSE;
					g_dwCallSetup = CALLSETUP_NONE;
					if (pCallList->dwCallsNumEntries > 0)
					{
						HCALL * hCalls = (HCALL *) ((BYTE *) pCallList + pCallList->dwCallsOffset);
						for(UINT i = 0; i < pCallList->dwCallsNumEntries; i++)
						{
							HCALL hCurCall = hCalls[i];
							DWORD dwState = GetCallStatus(hCurCall);
							if (!(dwState & (LINECALLSTATE_DISCONNECTED | LINECALLSTATE_IDLE)))
							{
								AddCall(hCurCall);
								SetCallState(hCurCall, dwState);
								if (IsVoiceCall(hCurCall))
								{
#ifdef DEBUG
									sprintf(szReply, "Call %i is a voice call", i);
									LogString(szReply, DEBUG_TAPI);
#endif
									CallIsVoice(hCurCall);
									switch (dwState)
									{
										// NOTE: g_dwCallSetup will reflect the last call in a setup state
										//       so we never set g_dwCallSetup to CALLSETUP_NONE
										case LINECALLSTATE_CONNECTED:
										{
											g_bInCall = TRUE;
											g_dwCallSetup = CALLSETUP_NONE;
											break;
										}
										case LINECALLSTATE_DIALING:
										{
											if (!g_bInCall)
												g_dwCallSetup = CALLSETUP_OUTGOING;
											break;
										}
										case LINECALLSTATE_PROCEEDING:
										case LINECALLSTATE_RINGBACK:
										{
											if (!g_bInCall)
												g_dwCallSetup = CALLSETUP_OUTRING;
											break;
										}
										case LINECALLSTATE_OFFERING:
										{
											if (!g_bInCall)
												g_dwCallSetup = CALLSETUP_INCOMING;
											break;
										}
										default:
											break;
									}
								}
							}
						}
					}
					LocalFree(pCallList);
				}
			}
			// get current registration state
			DWORD dwReg = 0;
			dwReturn = lineGetRegisterStatus(g_hVoiceLine, &dwReg);
			if (!dwReturn)
			{
				g_bRoaming = (dwReg == LINEREGSTATUS_ROAM) ? TRUE : FALSE;
				g_bRoaming = (g_dwInvertRoam) ? !g_bRoaming : g_bRoaming;
				g_bService = (dwReg > LINEREGSTATUS_ATTEMPTING) ? TRUE : FALSE;
			}
		}
	}
	if (g_hVoiceLine == NULL)
	{
		CleanupTAPI(); // if we didn't find a voice line, set system to uninitialized
		return LINEERR_NODEVICE;
	}
    return ERR_NONE;
}
